package com.simpou.commons.utils.file;

import com.simpou.commons.utils.file.filter.PatternFileNameFilter;
import com.simpou.commons.utils.string.StringLocales;
import com.simpou.commons.utils.string.Strings;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;


/**
 * Operações úteis para i18n.
 *
 * @author Jonas Pereira
 * @since 2011-06-23
 * @version 2012-05-15
 */
public class BundleHelper {
    /**
     * Gerenciador de recursos i18n.
     */
    private final ResourceBundle bundle;

    /**
     * Usado para escrever propriedades solicitadas não existentes.
     */
    private final List<PropertyHelper> propertyHelpers;

    /**
     * Locale.
     */

    //private final Locale locale;
    /**
     * Inicializa os recursos.
     *
     * @param bundlePath Nome base dos arquivos de recursos.
     * @throws java.io.IOException java.io.IOException.
     */
    public BundleHelper(final String bundlePath) throws IOException {
        this(bundlePath, null);
    }

    /**
     * Inicializa os recursos.
     *
     * @param bundlePath Nome base dos arquivos de recursos.
     * @param locale Locale. Se null o padrão é usado.
     * @throws java.io.IOException java.io.IOException.
     */
    public BundleHelper(final String bundlePath, final Locale locale)
        throws IOException {
        this(bundlePath, locale, getLocalesAvaiable(bundlePath));
    }

    /**
     * Inicializa os recursos.
     *
     * @param bundlePath Nome base dos arquivos de recursos.
     * @param locale Locale. Se null o padrão é usado.
     * @param localesAvaiable Locales a serem considerados.
     * @throws java.io.IOException java.io.IOException.
     */
    public BundleHelper(final String bundlePath, final Locale locale,
        final Set<Locale> localesAvaiable) throws IOException {
        if (locale == null) {
            bundle = ResourceBundle.getBundle(bundlePath);
        } else {
            bundle = ResourceBundle.getBundle(bundlePath, locale);
        }

        String bundleFullPath;
        File bundleFile;

        propertyHelpers = new ArrayList<PropertyHelper>(localesAvaiable.size() +
                1);

        // adiciona o bundle padrão
        bundleFile = FileHelper.getResourceFile(bundlePath +
                PropertyHelper.FILE_PROPS_EXTENSION);

        bundleFullPath = bundleFile.getCanonicalPath();
        propertyHelpers.add(new PropertyHelper(bundleFullPath));

        // adiciona os demais bundles disponíveis
        for (Locale localeAvaiable : localesAvaiable) {
            bundleFile = FileHelper.getResourceFile(bundlePath + "_" +
                    localeAvaiable.toString() +
                    PropertyHelper.FILE_PROPS_EXTENSION);
            bundleFullPath = bundleFile.getCanonicalPath();
            propertyHelpers.add(new PropertyHelper(bundleFullPath));
        }
    }

    /**
     * <p>Getter for the field
     * <code>bundle</code>.</p>
     *
     * @param key Chave definida no arquivo de bundle.
     * @return Mensagem i18n sem parâmetros.
     * @throws java.io.IOException Erro ao inserir propriedade inexistente no
     * arquivo.
     */
    public final String getBundle(final String key) throws IOException {
        return getBundleCreateOnMissing(0, key);
    }

    /**
     * <p>Getter for the field
     * <code>bundle</code>.</p>
     *
     * @param key Chave definida no arquivo de bundle.
     * @param params Parâmetros. No arquivo deve estar na forma: {0},{1},...
     * @return Mensagem i18n com parâmetros.
     * @throws java.io.IOException Erro ao inserir propriedade inexistente no
     * arquivo.
     */
    public final String getBundle(final String key, final String... params)
        throws IOException {
        String text = getBundleCreateOnMissing(params.length, key);

        if (params.length > 0) {
            text = Strings.replaceParams(text, params);
        }

        return text;
    }

    /**
     * Fecha fluxo de leitura de propriedades.
     *
     * @throws java.io.IOException Erro ao fechar arquivo de properties.
     */
    public final void close() throws IOException {
        for (PropertyHelper propertyHelper : propertyHelpers) {
            propertyHelper.close();
        }
    }

    /**
     * <p>getLocalesAvaiable.</p>
     * Este método pode não funcionar se aplicação estiver executando sob um
     * container cuja localização dos arquivos de deploy é desconhecida.
     *
     * @param bundlePath Nome do arquivo principal de i18n.
     * @return Lista dos locales definidos pelos arquivos de bundle presentes.
     * Locale padrão não é incluído na lista.
     * @throws FileNotFoundException Arquivo principal de bundle não encontrado.
     */
    public static Set<Locale> getLocalesAvaiable(final String bundlePath)
        throws FileNotFoundException {
        String bundlePackage;
        String bundleName;

        File fileAux = new File(bundlePath);
        bundlePackage = fileAux.getParent();
        bundleName = fileAux.getName();

        if (bundlePackage == null) {
            bundlePackage = "";
        }

        File root = FileHelper.getResourceFile(bundlePackage);

        File[] files = root.listFiles(new PatternFileNameFilter("^" +
                    bundleName + "(_\\w{2}(_\\w{2})?)?\\.properties$"));
        String language;
        Set<Locale> locales = new HashSet<Locale>();

        if (files != null) {
            Locale locale;

            for (File file : files) {
                language = file.getName()
                               .replaceAll("^" + bundleName +
                        "(_)?|\\.properties$", "");

                if (!language.isEmpty()) {
                    locale = StringLocales.getStringAsLocale(language);
                    locales.add(locale);
                }
            }
        }

        return locales;
    }

    /**
     * Lê o valor de um bundle e tenta criá-lo caso não exista no arquivo.
     *
     * @param params Número de parâmetros esperados.
     * @param key Chave definida no arquivo de bundle.
     * @return Mensagem i18n, se não existir, é criada e retorna string vazia.
     * @throws IOException Erro ao inserir propriedade inexistente no arquivo.
     */
    private String getBundleCreateOnMissing(final int params, final String key)
        throws IOException {
        String txt = null;

        try {
            txt = bundle.getString(key);
        } catch (MissingResourceException e) {
            for (PropertyHelper propertyHelper : propertyHelpers) {
                // cria chave se não encontrar
                txt = propertyHelper.setProperty(key, params);
            }
        }

        return txt;
    }
}
